use clap::Parser;
use std::fs;
use std::process::Command;

#[derive(Parser)]
struct Cli {
    #[arg(short, long)]
    pid: Option<u32>,
}

#[derive(Default)]
struct Data {
    cycle: u32,
    instr: u32,
    local_miss: u32,
    remote_miss: u32,
    l3_hit: u32,
    l3_miss: u32,
}

#[derive(Default)]
struct Temp {
    usr: f64,
    sys: f64,
    cpu: f64,
    page_fault: f64,
    mem: f64,
    force_switch: f64,
}

fn main() {
    let mut pid: u32 = u32::MAX;
    let pname: String;
    let pidarg = match Cli::parse().pid {
        Some(s) => {
            pid = s;
            format!("-p {}", s)
        }
        None => "-a".to_string(),
    };
    if pidarg != "-a" && pid != u32::MAX {
        pname = fs::read_to_string(format!("/proc/{}/comm", { pid })).expect("invalid process id");
        println!("Debug pid is {},name {}", &pidarg, pname);
    } else {
        println!("Debug pid is {}", &pidarg);
    }
    let mut data = Data::default();
    perfstat(&pidarg, &mut data);
    let mut info_ = Temp::default();
    pidstat(&pidarg, &mut info_);
    if data.cycle != 0 && data.instr != 0 {
        println!(
            "Instructions {: >10}\tCycles      {: >10}\tIPC       {:<6.2}",
            data.instr,
            data.cycle,
            (data.instr as f64) / (data.cycle as f64)
        )
    }
    if data.local_miss != 0 && data.remote_miss != 0 {
        println!(
            "Local Miss   {: >10}\tRemote Miss {: >10}\tLocalRate {:<6.2}",
            data.local_miss,
            data.remote_miss,
            (data.local_miss as f64) / (data.local_miss + data.remote_miss) as f64
        );
    }
    if data.l3_hit != 0 && data.l3_miss != 0 {
        println!(
            "L3 hit       {: >10}\tL3 miss     {: >10}\tHitRate   {:<6.2}",
            data.l3_hit,
            data.l3_miss,
            data.l3_hit as f64 / (data.l3_hit + data.l3_miss) as f64
        );
    }
    if pidarg != "-a" {
        println!(
        "%USR        {:>6.2}\n%SYS        {:>6.2}\n%CPU        {:>6.2}\nPage_Fault  {:>6.2}\n%mem        {:>6.2}\nForce_switch{:>6.2}",
        info_.usr, info_.sys, info_.cpu, info_.page_fault, info_.mem, info_.force_switch
    );
    }
}

fn perfstat(pidarg: &str, data: &mut Data) {
    let cmd = Command::new("perf").arg("stat").arg(pidarg).args(["-e","cycles,instructions,mem_load_l3_miss_retired.local_dram ,mem_load_l3_miss_retired.remote_dram,mem_load_retired.l3_hit,mem_load_retired.l3_miss"]).args(["--timeout","2000"]).output().expect("failed to execute perf tool");

    let mut cmdout = String::from_utf8_lossy(&cmd.stdout);
    if cmd.stdout.is_empty() && !cmd.stderr.is_empty() {
        cmdout = String::from_utf8_lossy(&cmd.stderr)
    }

    for line in cmdout.lines() {
        if !line.is_empty() && line.contains("cycles") {
            if line.trim_start().split_whitespace().nth(0).unwrap() == "<not" {
                println!("not counted cycle");
                continue;
            }
            data.cycle = line
                .trim_start()
                .split_whitespace()
                .nth(0)
                .unwrap()
                .replace(",", "")
                .parse::<u32>()
                .unwrap();
            //break;
            continue;
        }
        if !line.is_empty() && line.contains("instructions") {
            if line.trim_start().split_whitespace().nth(0).unwrap() == "<not" {
                println!("not counted instructions");
                continue;
            }
            data.instr = line
                .trim_start()
                .split_whitespace()
                .nth(0)
                .unwrap()
                .replace(",", "")
                .parse::<u32>()
                .unwrap();
            //break;
            continue;
        }
        if !line.is_empty() && line.contains("local_dram") {
            if line.trim_start().split_whitespace().nth(0).unwrap() == "<not" {
                println!("not counted local miss");
                continue;
            }
            data.local_miss = line
                .trim_start()
                .split_whitespace()
                .nth(0)
                .unwrap()
                .replace(",", "")
                .parse::<u32>()
                .unwrap();
            //break;
            continue;
        }
        if !line.is_empty() && line.contains("remote_dram") {
            if line.trim_start().split_whitespace().nth(0).unwrap() == "<not" {
                println!("not counted remote_miss");
                continue;
            }
            data.remote_miss = line
                .trim_start()
                .split_whitespace()
                .nth(0)
                .unwrap()
                .replace(",", "")
                .parse::<u32>()
                .unwrap();
            //break;
            continue;
        }
        if !line.is_empty() && line.contains("l3_hit") {
            if line.trim_start().split_whitespace().nth(0).unwrap() == "<not" {
                println!("not counted l3 hit");
                continue;
            }
            data.l3_hit = line
                .trim_start()
                .split_whitespace()
                .nth(0)
                .unwrap()
                .replace(",", "")
                .parse::<u32>()
                .unwrap();
            //break;
            continue;
        }
        if !line.is_empty() && line.contains("l3_miss") {
            if line.trim_start().split_whitespace().nth(0).unwrap() == "<not" {
                println!("not counted l3 miss");
                continue;
            }
            data.l3_miss = line
                .trim_start()
                .split_whitespace()
                .nth(0)
                .unwrap()
                .replace(",", "")
                .parse::<u32>()
                .unwrap();
            //break;
            continue;
        }
    }
}
fn pidstat(pid: &str, info: &mut Temp) {
    let args: Vec<&str>;
    if pid == "-a" {
        return;
    } else {
        let id = pid.split_whitespace().nth(1).unwrap();
        args = vec!["-p", id, "2", "1"];
    }
    let cmd = Command::new("pidstat")
        .args(["-u", "-r", "-w", "-I"])
        .args(args)
        .output()
        .expect("failed to execute pidstat tool");

    let mut cmdout = String::from_utf8_lossy(&cmd.stdout);
    if cmdout.is_empty() || !cmd.stderr.is_empty() {
        cmdout = String::from_utf8_lossy(&cmd.stderr);
    }
    let lines: Vec<&str> = cmdout.lines().collect();
    let mut idx = 0;
    while idx < lines.len() {
        if lines[idx].starts_with("A") {
            if lines[idx].contains("usr") {
                let cpuusage = lines[idx + 1];
                let tripple: Vec<&str> = cpuusage.split_whitespace().collect();
                let (usr, sys, cpu) = (tripple[3], tripple[4], tripple[7]);
                info.usr = usr.parse::<f64>().unwrap();
                info.sys = sys.parse::<f64>().unwrap();
                info.cpu = cpu.parse::<f64>().unwrap();
                idx += 1;
                continue;
            }
            if lines[idx].contains("minflt") {
                let meminfo = lines[idx + 1];
                let memusage: Vec<&str> = meminfo.split_whitespace().collect();
                info.page_fault = memusage[4].parse::<f64>().unwrap();
                info.mem = memusage[7].parse::<f64>().unwrap();
                idx += 1;
                continue;
            }

            if lines[idx].contains("cswch") {
                info.force_switch = lines[idx + 1]
                    .split_whitespace()
                    .nth(4)
                    .unwrap()
                    .parse::<f64>()
                    .unwrap();
                idx += 1;
                continue;
            }
        }
        idx += 1;
    }
}
